package com.crl.rcdp.gateway.filter;

import com.cars.util.auth.AuthDTO;
import com.cars.util.date.DateUtil;
import com.cars.util.global.GlobalConst;
import com.cars.util.global.GlobalReturnCode;
import com.cars.util.global.StatusEnum;
import com.cars.util.json.JsonResultUtil;
import com.cars.util.json.JsonUtil;
import com.cars.util.jwt.JwtResult;
import com.cars.util.jwt.JwtUtil;
import com.crl.rcdp.gateway.util.IPUtil;
import com.crl.rcdp.sys.model.SysAppApi;
import com.crl.rcdp.sys.model.SysUserRole;
import com.crl.rcdp.sys.service.SysAppService;
import com.crl.rcdp.sys.service.SysRoleService;
import com.crl.rcdp.sys.service.SysUserService;
import com.crl.redis.RedisKey;
import com.crl.redis.RedisUtil;
import io.netty.util.internal.StringUtil;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Pattern;

/**
 * @description:
 * @author: 孔垂云
 * @version: 1.0
 * @date: 2020/7/12 19:31
 */
public class TokenFilter implements GlobalFilter, Ordered {
    @Value("${gatewayPermitUrl}")
    private String permitUrl;//定义不需要校验的url
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private SysUserService sysUserService;
    @Autowired
    private SysRoleService sysRoleService;
    @Autowired
    private SysAppService sysAppService;

    Logger logger = LoggerFactory.getLogger(TokenFilter.class);


    /**
     * 存储无需鉴权的请求列表
     */
    private ArrayList<String> permitUrlList = null;

    /**
     * 将通配符表达式转化为正则表达式
     *
     * @param path
     * @return
     */
    private String getRegPath(String path) {
        char[] chars = path.toCharArray();
        int len = chars.length;
        StringBuilder sb = new StringBuilder();
        boolean preX = false;
        for (int i = 0; i < len; i++) {
            if (chars[i] == '*') {//遇到*字符
                if (preX) {//如果是第二次遇到*，则将**替换成.*
                    sb.append(".*");
                    preX = false;
                } else if (i + 1 == len) {//如果是遇到单星，且单星是最后一个字符，则直接将*转成[^/]*
                    sb.append("[^/]*");
                } else {//否则单星后面还有字符，则不做任何动作，下一把再做动作
                    preX = true;
                    continue;
                }
            } else {//遇到非*字符
                if (preX) {//如果上一把是*，则先把上一把的*对应的[^/]*添进来
                    sb.append("[^/]*");
                    preX = false;
                }
                if (chars[i] == '?') {//接着判断当前字符是不是?，是的话替换成.
                    sb.append('.');
                } else {//不是?的话，则就是普通字符，直接添进来
                    sb.append(chars[i]);
                }
            }
        }
        return sb.toString();
    }

    /**
     * 通配符模式
     *
     * @param excludePath - 不过滤地址
     * @param reqUrl      - 请求地址
     * @return
     */
    /*private boolean filterUrls(String excludePath, String reqUrl) {
        String regPath = getRegPath(excludePath);
        return Pattern.compile(regPath).matcher(reqUrl).matches();
    }*/

    /**
     * 判断请求是否是直接路由请求
     *
     * @param url 请求url
     * @return true：直接路由
     */
    private boolean isPermit(String url) {
        if (permitUrlList == null) {
            permitUrlList = new ArrayList<>();
            Collections.addAll(permitUrlList, permitUrl.split(","));
           /* permitUrlList.stream().filter(str -> {
                str = getRegPath(str);
                return true;
            }).collect(Collectors.toList());*/
            int i = 0;
            for (String rule : permitUrlList) {//把url转换成正则通配字符串
                rule = getRegPath(rule);
                permitUrlList.set(i, rule);
                i++;
            }
        }

        for (String rule : permitUrlList) {
            if (Pattern.compile(rule).matcher(url).matches()) {
                return true;
            }
        }
       /* for (String rule : permitUrlList) {
            if (url.startsWith(rule)) {
                return true;
            }
        }
        if (permitUrlList.stream().anyMatch(url::equals)) {
//            System.out.println("无需校验" + url);
            return true;
        }*/
       /* for (String serverName :
                permitServer.split(",")) {
            Pattern permitPattern = Pattern.compile("^" + serverName + "\\D*" + permitServiceUrl + "$");
            boolean permitServerResult = permitPattern.matcher(url).matches();
            if (permitServerResult) {
                return true;
            }
        }*/
        return false;
    }

    /**
     * 校验方式
     * 1、先校验该url是否可以直接请求
     * 2、再判断Authorization是否为空，如果不为空，校验jwt
     * 3、判断accessToken是否为空，如果不为空，校验jwt
     * 4、判断appid是否为空，如果不为空，校验appid
     *
     * @param exchange
     * @param chain
     * @return
     */

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //共有三种校验方式，appid
        ServerHttpRequest servletRequest = exchange.getRequest();
        //判断是否需要校验权限
        String servletPath = servletRequest.getPath().toString();//获取请求url
        System.out.println("请求url:" + servletPath);
        ServerHttpResponse response = exchange.getResponse();
        // 无需鉴权
        if (isPermit(servletPath)) {
            return chain.filter(exchange);
        }
        String authorization = servletRequest.getHeaders().getFirst("Authorization");//获取authorization
        String appid = servletRequest.getHeaders().getFirst("appid");//appid
        String accessToken = servletRequest.getHeaders().getFirst("accessToken");//accessToken
        if (StringUtil.isNullOrEmpty(authorization) && StringUtil.isNullOrEmpty(appid) && StringUtil.isNullOrEmpty(accessToken)) {
            //未授权
            return this.returnUnAuth(response, GlobalReturnCode.NO_AUTHORIZATION);
        }
        //按照authorization进行校验
        if (StringUtils.isNotBlank(authorization)) {
            //校验jwt
            JwtResult jwtResult = JwtUtil.parseJWT(authorization);
            boolean isValid = jwtResult.isValid();
            // 校验accessToken是否有效
            if (!isValid) {
                return this.returnUnAuth(response, GlobalReturnCode.INVALID_TOKEN);
            }
            //校验权限
            AuthDTO authDTO = redisUtil.get(RedisKey.AUTH_PASSWORD_KEY + JwtUtil.getJWTOwner(authorization), AuthDTO.class);
            if (authDTO == null) {//redis不存在
                return this.returnUnAuth(response, GlobalReturnCode.INVALID_TOKEN);
            }
            //url不在权限内
            if (!this.checkRolesContaisPath(authDTO.getListRoles(), servletPath)) {
                return this.returnUnAuth(response, GlobalReturnCode.NO_PERMISSION_TOKEN);
            }
            return chain.filter(exchange);
        }
        //按照accessToken进行校验
        if (StringUtils.isNotBlank(accessToken)) {
            JwtResult jwtResult = JwtUtil.parseJWT(accessToken);
            boolean isValid = jwtResult.isValid();
            // 校验accessToken是否有效
            if (!isValid) {
                //token不合法
                return this.returnUnAuth(response, GlobalReturnCode.INVALID_TOKEN);
            }
            //从redis根据owner获取用户的基本信息，包括权限，是否过期这些
            AuthDTO authDTO = redisUtil.get(RedisKey.AUTH_ACCESSTOKEN_KEY + JwtUtil.getJWTOwner(accessToken), AuthDTO.class);
            if (authDTO == null) {//redis不存在
                return this.returnUnAuth(response, GlobalReturnCode.INVALID_TOKEN);
            }
            //判断是否已过期
            if (authDTO.getValidDate().compareTo(DateUtil.getSystemDate()) < 0) {
                return this.returnUnAuth(response, GlobalReturnCode.EXPIRED);
            }
            //判断app是否禁用
            if (authDTO.getStatus().equals(StatusEnum.DISABLED.getValue())) {
                return this.returnUnAuth(response, GlobalReturnCode.DISABLED);
            }

            //url不在权限内
            if (!authDTO.getUrls().containsKey(servletPath)) {
                return returnUnAuth(response, GlobalReturnCode.NO_PERMISSION_TOKEN);
            }
//            //判断权限
//            if (!checAppidContainsPath(jwtResult.getOwner(), servletPath)) {
//
//            }
            return chain.filter(exchange);
        }
        //按照appid进行校验
        if (StringUtils.isNotBlank(appid)) {
            String ip = IPUtil.getIpAddress(servletRequest);
            AuthDTO authDTO = redisUtil.get(RedisKey.AUTH_APPID_KEY + appid, AuthDTO.class);
            if (authDTO == null) {//redis不存在
                authDTO = sysAppService.getAuthDTOByAppid(appid);
                if (StringUtil.isNullOrEmpty(authDTO.getValidDate())) {
                    return this.returnUnAuth(response, GlobalReturnCode.INVALID_APPID);
                }
            }
            if (ip.equals("0:0:0:0:0:0:0:1")) {
                ip = "127.0.0.1";
            }
            //判断ip
            if (!IPUtil.isValidIp(ip, authDTO.getIps())) {//TODO完善校验ip的方法
                return returnUnAuth(response, GlobalReturnCode.IP_INVALID);
            }
            //判断用户已过期
            if (authDTO.getValidDate().compareTo(DateUtil.getSystemDate()) < 0) {
                return this.returnUnAuth(response, GlobalReturnCode.EXPIRED);
            }
            //判断app是否禁用
            if (authDTO.getStatus() != null && authDTO.getStatus().equals(StatusEnum.DISABLED.getValue())) {
                return this.returnUnAuth(response, GlobalReturnCode.DISABLED);
            }

            //url不在权限内
            if (!authDTO.getUrls().containsKey(servletPath)) {
                return returnUnAuth(response, GlobalReturnCode.NO_PERMISSION_TOKEN);
            }
            return chain.filter(exchange);
        }

//        String token = exchange.getRequest().getHeaders().getFirst("token");
//        if (token == null || token.isEmpty()) {
//            logger.info("token is empty...");
//            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
//            return exchange.getResponse().setComplete();
//        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return -100;
    }

    /**
     * 根据userId获取对应的角色列表，先从redis获取，如果没有，从数据库查，然后赋值到redis
     *
     * @param userId
     * @return
     */
    private List<SysUserRole> listUserRoles(int userId) {
        List<SysUserRole> list = redisUtil.get(RedisKey.USER_ROLES_KEY + userId, List.class);
        if (list == null) {
            list = sysUserService.listUseRoles(userId);
            redisUtil.set(RedisKey.USER_ROLES_KEY + userId, list);
        }
        return list;
    }

    /**
     * 根据角色获取对应的path
     *
     * @param roleId
     * @return
     */
    private HashMap<String, String> listRolePaths(int roleId) {
        HashMap<String, String> hashMap = redisUtil.get(RedisKey.ROLE_PATHS_KEY + roleId, HashMap.class);
        if (hashMap == null) {
            hashMap = sysRoleService.listRolePaths(roleId);
            redisUtil.set(RedisKey.ROLE_PATHS_KEY + roleId, GlobalConst.TOKEN_EXPIRED, GlobalConst.TOKEN_EXPIRED);
        }
        return hashMap;
    }

    /**
     * 判断用户是否具备该权限
     *
     * @param userId
     * @param path
     * @return
     */
    private boolean checkUserContainsPath(int userId, String path) {
        List<SysUserRole> list = this.listUserRoles(userId);
        for (SysUserRole sysUserRole : list) {
            HashMap<String, String> hashMap = this.listRolePaths(sysUserRole.getRoleId());
            if (hashMap.containsKey(path)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 返回未授权
     *
     * @param response
     */
    private Mono<Void> returnUnAuth(ServerHttpResponse response, GlobalReturnCode globalReturnCode) {
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        DataBuffer buffer = response.bufferFactory().wrap(JsonUtil.toStr(JsonResultUtil.error(globalReturnCode)).getBytes());
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }

    /**
     * 判断用户是否具备该权限
     *
     * @param appid
     * @param path
     * @return
     */
    private boolean checAppidContainsPath(String appid, String path) {
        HashMap<String, String> hashMap = redisUtil.get(RedisKey.APPID_PATH_KEY + appid, HashMap.class);
        if (hashMap == null) {
            hashMap = new HashMap<>();
            List<SysAppApi> list = sysAppService.listApiByAppid(appid);
            for (SysAppApi sysAppApi : list) {
                hashMap.put(sysAppApi.getUrl(), sysAppApi.getUrl());
            }
            redisUtil.set(RedisKey.APPID_PATH_KEY + appid, hashMap, GlobalConst.TOKEN_EXPIRED);
        }
        return hashMap.containsKey(path);
    }

    /**
     * 判断角色列表是否含有对应的url
     *
     * @param listRole 角色列表
     * @param path
     * @return
     */
    private boolean checkRolesContaisPath(List<Integer> listRole, String path) {
        for (Integer roleId : listRole) {
            HashMap<String, String> hashRolePath = redisUtil.get(RedisKey.AUTH_ROLE_PATH + roleId, HashMap.class);
            if (hashRolePath == null) {
                hashRolePath = sysRoleService.listRolePaths(roleId);
                redisUtil.set(RedisKey.AUTH_ROLE_PATH + roleId, hashRolePath);
            }
            if (hashRolePath.containsKey(path)) {
                return true;
            }
        }
        return false;
    }


}
